﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using Orchard.Caching;
using Orchard.ContentManagement;
using So.ImageResizer.Helpers;
using So.ImageResizer.Models;
using Resizer = ImageResizer;
using System.Text.RegularExpressions;
using Orchard;

namespace So.ImageResizer.Services
{
    public class ImageResizerService : IImageResizerService
    {
        private readonly ISignals _signals;
        private readonly ICacheManager _cacheManager;
        private readonly IOrchardServices _orchardServices;

        public ImageResizerService(IOrchardServices orchardServices, ICacheManager cacheManager, ISignals signals)
        {
            _orchardServices = orchardServices;
            _cacheManager = cacheManager;
            _signals = signals;
        }

        /// <summary>
        /// http://en.csharp-online.net/Create_a_MD5_Hash_from_a_string
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public string CreateMd5Hash(string input)
        {
            // Use input string to calculate MD5 hash
            MD5 md5 = MD5.Create();
            byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
            byte[] hashBytes = md5.ComputeHash(inputBytes);

            // Convert the byte array to hexadecimal string
            var sb = new StringBuilder();
            for (int i = 0; i < hashBytes.Length; i++)
            {
                sb.Append(hashBytes[i].ToString("X2"));
                // To force the hex string to lower-case letters instead of
                // upper-case, use he following line instead:
                // sb.Append(hashBytes[i].ToString("x2")); 
            }
            return sb.ToString();
        }

        public string GetMimeTypeByImageExtesion(string fileExtention)
        {
            string retVal = ImageTypes.Jpg;
            switch (fileExtention.ToLower())
            {
                case "bmp":
                    retVal = ImageTypes.Bmp;
                    break;
                case "jpeg":
                    retVal = ImageTypes.Jpg;
                    break;
                case "png":
                    retVal = ImageTypes.Png;
                    break;
                case "gif":
                    retVal = ImageTypes.Gif;
                    break;
                case "exif":
                    retVal = ImageTypes.Exif;
                    break;
                case "tif":
                    retVal = ImageTypes.Tif;
                    break;
                case "tiff":
                    retVal = ImageTypes.Tiff;
                    break;
                case "tff":
                    retVal = ImageTypes.Tff;
                    break;
                case "jpe":
                    retVal = ImageTypes.Jpe;
                    break;
                case "jif":
                    retVal = ImageTypes.Jif;
                    break;
                case "jfif":
                    retVal = ImageTypes.Jfif;
                    break;
                case "jfi":
                    retVal = ImageTypes.Jfi;
                    break;
            }
            return retVal;
        }

        public IEnumerable<string> SupportedFileExtensions()
        {
            return Resizer.ImageBuilder.Current.GetSupportedFileExtensions();
        }

        public string ResizeImage(string url, int width = 0, int height = 0, int maxWidth = 0, int maxheight = 0, Resizer.CropMode cropMode = Resizer.CropMode.Auto,
                                  Resizer.ScaleMode scale = Resizer.ScaleMode.Both, Resizer.StretchMode stretchMode = Resizer.StretchMode.Proportionally)
        {
            string imagePath = HttpContext.Current.Server.MapPath(url);

            // check if width or height is smaller then original
            if (MustBeScaledDown(imagePath, width, height))
            {
                //build up cached image path
                string hasedProperties = CreateMd5Hash(string.Format("{0}-{1}-{2}-{3}-{4}", width, height, maxWidth, maxheight, cropMode));
                string cachedFileName = string.Format("{0}-{1}.{2}", Path.GetFileNameWithoutExtension(url), hasedProperties, GetCleanFileExtension(url));
                string cachedImagePath = string.Concat(CacheFolderPath, Path.GetDirectoryName(url.Replace("~", "")), cachedFileName);

                //check if file already exists
                if (!File.Exists(cachedImagePath))
                {
                    string cachedImageDir = Path.GetDirectoryName(cachedImagePath);
                    if (cachedImageDir != null && !Directory.Exists(cachedImageDir))
                    {
                        Directory.CreateDirectory(cachedImageDir);
                    }

                    //todo handle errors!..maxheight exeeded etc.
                    var settings = new Resizer.ResizeSettings() { Width = width, Height = height, CropMode = cropMode, MaxHeight = maxheight, MaxWidth = maxWidth, Stretch = stretchMode, Scale = scale };
                    Resizer.ImageBuilder.Current.Build(imagePath, cachedImagePath, settings);
                }
                imagePath = cachedImagePath;
            }
            return imagePath;
        }

        public string GetCleanFileExtension(string url)
        {
            var extension = Path.GetExtension(url);
            if (extension != null)
            {
                return extension.Replace(".", "").ToLower();
            }
            return string.Empty;
        }

        public string BuildResizeImageUrl(string url, int width, int height, int maxWidth, int maxHeight, ResizeSettingType.CropMode crop = ResizeSettingType.CropMode.None,
                                          ResizeSettingType.ScaleMode scale = ResizeSettingType.ScaleMode.Both, ResizeSettingType.StretchMode stretch = ResizeSettingType.StretchMode.Proportionally)
        {
            var queryString = new QueryString()
                .Add("url", url)
                .Add("width", width.ToString(CultureInfo.InvariantCulture))
                .Add("height", height.ToString(CultureInfo.InvariantCulture))
                .Add("maxWidth", maxWidth.ToString(CultureInfo.InvariantCulture))
                .Add("maxheight", maxHeight.ToString(CultureInfo.InvariantCulture))
                .Add("cropMode", crop.ToString())
                .Add("scale", scale.ToString())
                .Add("stretchMode", stretch.ToString());
            return string.Format("/resizedImage{0}", queryString);
        }

        public ResizerSettingsModel GetImageResizerSettings()
        {
            //todo global cache key!
            var retVal = _cacheManager.Get("ImageResizerSettingsPartChanged", ctx =>
            {
                var resizerSettings = _orchardServices.WorkContext.CurrentSite.As<ImageResizerSettingsPart>();
                return new ResizerSettingsModel()
                {
                    ResizerHttpModuleEnabled = resizerSettings.ResizerHttpModuleEnabled
                };
            });
            return retVal;
        }

        public string UpdateAllImageSourcesToResizedUrlsFromHtml(string html)
        {
            if (string.IsNullOrEmpty(html))
            {
                return string.Empty;
            }

            var imgRegex = new Regex(@"<img\s[^>]*>(?:\s*?</img>)?");
            var matches = imgRegex.Matches(html).OfType<Match>().OrderByDescending(m => m.Groups[0].Index);

            foreach (var match in matches)
            {
                var index = match.Groups[0].Index;
                var length = match.Groups[0].Length;
                string url = match.Groups[0].Value.Trim();
                if (!string.IsNullOrEmpty(url))
                {
                    if (url[0] == '~')
                    {
                        url = VirtualPathUtility.ToAbsolute(url);
                    }

                    int parsedHeight = 0;
                    int parsedWidth = 0;
                    string src = "";

                    var htmlDoc = new HtmlAgilityPack.HtmlDocument();
                    htmlDoc.LoadHtml(url);
                    var attributes = htmlDoc.DocumentNode.FirstChild.Attributes.ToList();

                    // get and set the height attribute
                    var heightAttr = attributes.FirstOrDefault(s => s.Name.ToLower() == "height");
                    if (heightAttr != null)
                    {
                        int.TryParse(heightAttr.Value, out parsedHeight);
                    }

                    // get and set the width attribute
                    var widthAttr = attributes.FirstOrDefault(s => s.Name.ToLower() == "width");
                    if (widthAttr != null)
                    {
                        int.TryParse(widthAttr.Value, out parsedWidth);
                    }

                    if (parsedWidth > 0 || parsedHeight > 0)
                    {
                        // get and set the src attribute
                        var srcAttr = attributes.FirstOrDefault(s => s.Name.ToLower() == "src");
                        if (srcAttr != null)
                        {
                            src = srcAttr.Value;
                        }

                        // now we've got the height and width we can replace the url so that it's using the imageresizer module
                        string resizerImageUrl = BuildResizeImageUrl(url: src, height: parsedHeight, maxHeight: parsedHeight, width: parsedWidth, maxWidth: parsedWidth);
                        html = html.Substring(0, index) + string.Format("<img src=\"{0}\" />", resizerImageUrl) + html.Substring(index + length);
                    }
                }
            }
            return html;
        }

        #region Extensions

        private static bool MustBeScaledDown(string imagePath, int width, int height)
        {
            Image image = Image.FromFile(imagePath, false);
            return (width > 0 || height > 0) && (image.Width > width || image.Height > height);
        }

        //todo maybe configurable in admin?
        private static readonly string CacheFolderPath = HttpContext.Current.Server.MapPath("~/Modules/So.ImageResizer/Cache");

        #endregion Extensions
    }
}